home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Celestin Apprentice 5
/
Apprentice-Release5.iso
/
Source Code
/
Libraries
/
VideoToolbox 96.06.15
/
VideoToolboxSources
/
GDTestClut.c
< prev
next >
Wrap
Text File
|
1996-03-26
|
19KB
|
572 lines
/*
GDTestClut.c
This is used by TimeVideo; I can’t think of any reason for including it in any
other program.
OSErr GDTestClut(FILE *o[2],GDHandle device,short flags,VideoInfo *card);
GDTestClut.c tests whether the video clut can be written and read faithfully,
and saves the results in a user-supplied VideoInfo structure. The test consists
of writing random numbers to all the clut entries, reading them back, and
comparing. I’ve been surprised to find more than a few video drivers that fail
this test, for various reasons.
The bits of “flags” are tested independently. If flags&testClutQuicklyFlag then
SetEntriesQuickly() will be tested instead of GDSetEntries/GDDirectSetEntries.
If flags&testClutSeriallyFlag then the clut entries will be set individually,
calling GDSetEntries once for each clut entry, to check the clut entry
addressing. If flags&testClutLinearFlag then a simple sequence, will be loaded
into the clut, instead of random numbers, to help figuring out systematic
errors. The sequence is (0,0,0),(1,0,0), (0,2,0),(0,0,3),(4,4,4),(4,0,0), and so
on.
Returned value is zero if ok, nonzero if error occurred.
GDTestClut recognizes the common driver errors and reports them in a sensible
way, using the various fields of the card->depth[d].clut structure. Errors
accumulate in the card->depth[d].clut structure, allowing you to make multiple
calls to GDTestClut and only then summarize the results. It is important that
you zero card->depth[d].clut.tests and card->depth[d].clutQuickly.tests before
your first call to GDTestClut, to induce it to zero the rest of the clut
structures.
Assumes that GDevice record is valid, i.e. the user has not called GDSetMode().
HISTORY:
3/9/93 dgp code extracted from the demo TestCluts.c, to create a reusable
subroutine.
3/10/93 dgp No longer assume that GDevice record reflects the actual state of the
driver.
4/5/93 dgp Add support for grayB.
4/19/93 dgp Fixed bug in VisibleHash that used garbage in place of
linear color table when gdType==directType. Use GDNewLinearColorTable.
5/11/93 dgp GDTestClutHash now checks for valid device, in response to bug report
by Jonathan Brecher.
5/18/93 dgp Allow small, 0.001, tolerance in establishing the identity transform.
5/25/93 dgp Increased proportion of time spent loading clut in VisibleHash().
9/5/94 dgp removed assumption in printf's that int==short.
12/29/94 dgp WriteAndReadClut now waits for vbl before reading.
4/8/95 dgp I introduced WAIT_FOR_VBL to enable/disable waiting for vbl between
write and read of CLUT. I introduce the wait to see if it would
help certain video cards pass the tests. However, in both cases
it made no difference and the explanation turned out to be that
cscSetGamma wasn't supported. So, in the interest of fast testing,
I'm omitting the wait.
3/7/96 dgp Fix program to reflect fact that cscSetEntries reads from table[0],....
whereas cscGetEntries writes to table[start],....
3/26/96 dgp increase tolerance for recognizing identity, allow insignificant bits to be garbage.
*/
#include "VideoToolbox.h"
//#include <Errors.h>
#include "GDInfo.h"
#define WAIT_FOR_VBL 0 // 0 for no wait; 1 to wait for vbl between write & read of CLUT.
// These functions are solely for use within this file.
ColorSpec *MakeClutTable(GDHandle device,short flags);
OSErr WriteClut(GDHandle device,ColorSpec putTable[],short flags);
OSErr ShowGammaTable(FILE *o[2],GDHandle device);
void RGBToGray(RGBColor *rgb,short dacSize);
Boolean UnequalClutEntry(RGBColor *a,RGBColor *b,short mask);
OSErr VisibleHash(GDHandle device,SetEntriesFunction function,short clutEntries
,short *hashPtr);
OSErr EstimateClutTransform(short flags,VideoInfo *card);
OSErr ComputeRMSClutError(FILE *file
,ColorSpec *putTable,ColorSpec *getTable,short flags,VideoInfo *card);
OSErr WriteAndReadClut(GDHandle device,short flags,ColorSpec **put,ColorSpec **get);
#define SixteenBitGray 1
OSErr GDTestClut(FILE *o[2],short flags,VideoInfo *card)
{
short i,j,clutSize,quickly,isGray;
int error;
ColorSpec *putTable=NULL,*getTable=NULL;
VideoCardClutTest *clut;
if(card->device==NULL)return 0;
isGray=!TestDeviceAttribute(card->device,gdDevType);
for(quickly=0;quickly<2;quickly++){
clut=&card->depth[card->d].clut[quickly][isGray];
if(!clut->read.tested){
for(j=0;j<3;j++)for(i=0;i<3;i++)clut->read.rgbGain[j][i]=NAN;
for(j=0;j<3;j++)clut->read.rgbError[j]=NAN;
for(j=0;j<3;j++)clut->read.rgbErrorAtOnce[j]=NAN;
clut->read.errors=0;
clut->read.errorsAtOnce=0;
}
if(!clut->visual.tested){
clut->visual.errors=0;
clut->visual.errorsAtOnce=0;
}
if(!clut->hash.tested){
clut->hash.errors=0;
}
}
quickly=((flags&testClutQuicklyFlag)!=0);
clut=&card->depth[card->d].clut[quickly][isGray];
if(!clut->read.doTest)return 0;
GDInfo(card);
if(card->device==NULL)return 0;
clutSize=card->depth[card->d].clutSize;
error=GDSaveGamma(card->device);
error=GDUncorrectedGamma(card->device);
if(error)return error;
error=EstimateClutTransform(flags&~testClutSeriallyFlag,card);
if(error)goto done;
error=WriteAndReadClut(card->device,flags,&putTable,&getTable);
if(error)goto done;
// COMPARE
ComputeRMSClutError(o[1],putTable,getTable,flags,card);
card->clutTested=1;
done:
DisposePtr((Ptr)putTable);
DisposePtr((Ptr)getTable);
GDRestoreGamma(card->device);
GDRestoreDeviceClut(card->device);
// clut->visual.doTest |= error && clut->hash.doTest;
// if(clut->visual.doTest)error=GDTestClutVisually(flags,card);
return error;
}
OSErr EstimateClutTransform(short flags,VideoInfo *card)
{
short i,j,tableSize,quickly,isGray;
int error=0;
ColorSpec *putTable,*getTable;
VideoCardClutTest *clut;
long putSum[3],getSum[3][3];
unsigned short *put3,*get3;
error=WriteAndReadClut(card->device,flags|testClutGains,&putTable,&getTable);
if(error)return error;
tableSize=GetPtrSize((Ptr)putTable)/sizeof(putTable[0]);
// Estimate transformation
for(i=0;i<3;i++){
putSum[i]=0;
for(j=0;j<3;j++)getSum[i][j]=0;
}
for(i=0;i<tableSize;i++){
put3=(unsigned short *)&putTable[i].rgb;
get3=(unsigned short *)&getTable[i].rgb;
putSum[i%3]+=put3[i%3];
for(j=0;j<3;j++)getSum[j][i%3]+=get3[j];
}
isGray=!TestDeviceAttribute(card->device,gdDevType);
quickly=((flags&testClutQuicklyFlag)!=0);
clut=&card->depth[card->d].clut[quickly][isGray];
for(i=0;i<3;i++)for(j=0;j<3;j++){
clut->read.rgbGain[j][i]=(double)getSum[j][i]/putSum[i];
if(clut->read.rgbGain[j][i]!=floor(clut->read.rgbGain[j][i]))
clut->read.rgbGain[j][i]+=0.5*tableSize/putSum[i];
}
DisposePtr((Ptr)putTable);
DisposePtr((Ptr)getTable);
return error;
}
OSErr WriteAndReadClut(GDHandle device,short flags,ColorSpec **put,ColorSpec **get)
{
short i,clutSize,tableSize;
ColorSpec spec,*putTable,*getTable;
VBLTaskAndA5 vbl;
int error=0;
clutSize=GDClutSize(device);
// MAKE TABLE FOR CLUT
*put=putTable=MakeClutTable(device,flags);
if(putTable==NULL)return MemError();
tableSize=GetPtrSize((Ptr)putTable)/sizeof(putTable[0]);
// MAKE BLANK TABLE
*get=getTable=(ColorSpec *)NewPtr(GetPtrSize((Ptr)putTable));
if(getTable==NULL)return MemError();
spec.value=spec.rgb.red=spec.rgb.green=spec.rgb.blue=0;
for(i=0;i<tableSize;i++)getTable[i]=spec;
// WRITE CLUT & READ BACK, ONE CLUT-FULL AT A TIME
vbl.subroutine=NULL; // request default subroutine
error=VBLInstall(&vbl,device,1000);
if(error)PrintfExit("%s line %d. WriteAndReadClut:VBLInstall error %d\n"
,__FILE__,__LINE__,error);
vbl.vbl.vblCount=1; // enable the interrupt service routine
for(i=0;i<tableSize;i+=clutSize){
error=WriteClut(device,&putTable[i],flags);
if(error)break;
vbl.newFrame=0;
if(WAIT_FOR_VBL)while(vbl.newFrame==0) ; // wait until clut's been loaded before reading it
error=GDGetEntries(device,0,clutSize-1,&getTable[i]); // Fixed 3/4/96
if(error)break;
}
VBLRemove(&vbl);
return error;
}
OSErr ComputeRMSClutError(FILE *file
,ColorSpec *putTable,ColorSpec *getTable,short flags,VideoInfo *card)
{
short i,j,k,clutSize,tableSize,quickly,isGray,integralGains;
unsigned short *put3,*get3,n[3],bad;
double e,squaredError[3],model[3];
VideoCardClutTest *clut;
int error=0;
isGray=!TestDeviceAttribute(card->device,gdDevType);
quickly=((flags&testClutQuicklyFlag)!=0);
clut=&card->depth[card->d].clut[quickly][isGray];
clutSize=GDClutSize(card->device);
tableSize=GetPtrSize((Ptr)putTable)/sizeof(putTable[0]);
// Estimate error of linear model
for(i=0;i<3;i++){
squaredError[i]=0;
n[i]=0;
}
bad=0;
// Allow error of a few least significant steps of dac
clut->read.tolerance=2.9*(1<<(16-GDDacSize(card->device)));
for(i=0;i<tableSize;i++){
put3=(unsigned short *)&putTable[i].rgb;
get3=(unsigned short *)&getTable[i].rgb;
for(j=0;j<3;j++){
model[j]=put3[0]*clut->read.rgbGain[j][0]
+put3[1]*clut->read.rgbGain[j][1]
+put3[2]*clut->read.rgbGain[j][2];
e=get3[j]-model[j];
bad|=fabs(e)>clut->read.tolerance;
squaredError[j]+=e*e;
n[j]++;
}
}
for(i=0;i<3;i++){
clut->read.rgbError[i]=sqrt(squaredError[i]/n[i]);
if(!(flags&testClutSeriallyFlag))clut->read.rgbErrorAtOnce[i]=sqrt(squaredError[i]/n[i]);
}
clut->read.identity=1;
e=1.999*pow(2,-GDDacSize(card->device)); // allow nonsignificant bits to be garbage
for(i=0;i<3;i++)for(j=0;j<3;j++)clut->read.identity&=(fabs(clut->read.rgbGain[i][j]-(i==j))<e);
if(bad){
clut->read.errors=1;
if(!(flags&testClutSeriallyFlag))clut->read.errorsAtOnce=1;
}
if(bad){
// Print one-line error message to file.
fprintf(file,"\n");
if(flags&testClutQuicklyFlag)fprintf(file,"SetEntriesQuickly != cscGetEntries. ");
else switch((*card->device)->gdType){
case fixedType:
break;
case clutType:
fprintf(file,"cscSetEntries != cscGetEntries. ");
break;
case directType:
fprintf(file,"cscDirectSetEntries != cscGetEntries. ");
break;
}
fprintf(file,"%d-bit ",(int)card->depth[card->d].pixelSize);
if(isGray)fprintf(file,"gray pixels.");
else fprintf(file,"color pixels.");
if(flags&testClutSeriallyFlag)fprintf(file," Loaded one clut entry at a time.\n");
else fprintf(file," Loaded whole clut at once.\n");
// Print the estimated color transformation matrix
fprintf(file,"\"expected\" assumes the best-fit linear color transformation matrix:\n");
integralGains=1;
for(j=0;j<3;j++)for(k=0;k<3;k++)
integralGains&=(clut->read.rgbGain[j][k]==floor(clut->read.rgbGain[j][k]));
for(j=0;j<3;j++){
fprintf(file," (%cOut±%4.1f%%)%c"
,"RGB"[j],clut->read.rgbError[j]*100./USHRT_MAX," = "[j]);
if(integralGains)fprintf(file,"(%1.0f %1.0f %1.0f)"
,clut->read.rgbGain[j][0],clut->read.rgbGain[j][1],clut->read.rgbGain[j][2]);
else fprintf(file,"(%3.2f %3.2f %3.2f)"
,clut->read.rgbGain[j][0],clut->read.rgbGain[j][1],clut->read.rgbGain[j][2]);
fprintf(file,"%c(%cIn)\n"," x "[j],"RGB"[j]);
}
// Print each clut error.
for(i=0;i<tableSize;i++){
put3=(unsigned short *)&putTable[i].rgb;
get3=(unsigned short *)&getTable[i].rgb;
bad=0;
for(j=0;j<3;j++){
model[j]=put3[0]*clut->read.rgbGain[j][0]
+put3[1]*clut->read.rgbGain[j][1]
+put3[2]*clut->read.rgbGain[j][2];
e=get3[j]-model[j];
bad|=fabs(e)>clut->read.tolerance;
}
if(0 && bad)fprintf(file,"Clut[%3d] wrote(%04u,%04u,%04u) "
"expected(%04.0f,%04.0f,%04.0f) but read(%04u,%04u,%04u)\n"
,i%clutSize,put3[0],put3[1],put3[2]
,model[0],model[1],model[2]
,get3[0],get3[1],get3[2]);
if(bad)fprintf(file,"Clut[%3d] wrote(%04x,%04x,%04x) "
"expected(%04x,%04x,%04x) but read(%04x,%04x,%04x)\n"
,i%clutSize,put3[0],put3[1],put3[2]
,(unsigned short)(0.5+model[0]),(unsigned short)(0.5+model[1]),(unsigned short)(0.5+model[2])
,get3[0],get3[1],get3[2]);
}
fprintf(file,"\n");
}
clut->read.tested=1;
return error;
}
OSErr GDTestClutVisually(short flags,VideoInfo *card)
{
int error;
short clutSize,quickly,isGray;
ColorSpec *putTable,*normalTable;
Boolean weirdError,normalError;
VideoCardClutTest *clut;
isGray=!TestDeviceAttribute(card->device,gdDevType);
quickly=((flags&testClutQuicklyFlag)!=0);
clut=&card->depth[card->d].clut[quickly][isGray];
if(clut->visual.tested==0){
clut->visual.errors=0;
clut->visual.errorsAtOnce=0;
}
if(card->device==NULL)return 0;
clutSize=GDClutSize(card->device);
normalTable=((**(**(**card->device).gdPMap).pmTable)).ctTable;
if(card->device==GetMainDevice())
putTable=MakeClutTable(card->device,testClutNegativeFlag);
else putTable=MakeClutTable(card->device,flags);
if(putTable==NULL)return MemError();
GDSaveGamma(card->device);
GDUncorrectedGamma(card->device);
error=WriteClut(card->device,putTable,flags);
if(error){
GDRestoreGamma(card->device);
GDRestoreDeviceClut(card->device);
return error;
}
printf(BLANKLINE);
printf("Screen should be weirdly colored; watch for subtle change as you hit return:" "\r");
while(getcharUnbuffered()==-1) ;
error=WriteClut(card->device,putTable,0);
if(error){
GDRestoreGamma(card->device);
GDRestoreDeviceClut(card->device);
return error;
}
printf(BLANKLINE);
printf("Did you see any change at all?");
weirdError=YesOrNo(0);
printf("\r");
error=WriteClut(card->device,normalTable,flags);
printf(BLANKLINE);
printf("Screen should be normal now; watch for subtle change as you hit return:" "\r");
while(getcharUnbuffered()==-1) ;
error=WriteClut(card->device,normalTable,0);
printf(BLANKLINE);
printf("Did you see any change at all?");
normalError=YesOrNo(0);
printf("\r");
GDRestoreGamma(card->device);
GDRestoreDeviceClut(card->device);
DisposePtr((Ptr)putTable);
clut->visual.tested++;
clut->visual.errors+=(weirdError || normalError);
if(!(flags&testClutSeriallyFlag))clut->visual.errorsAtOnce+=(weirdError || normalError);
return 0;
}
ColorSpec *MakeClutTable(GDHandle device,short flags)
{
short i,j,clutSize;
RGBColor put;
ColorSpec *table;
clutSize=GDClutSize(device);
if((flags&testClutGains) && clutSize<16)clutSize=16;
table=(ColorSpec *)NewPtr(sizeof(*table)*clutSize);
if(table==NULL)return table;
for(i=0;i<clutSize;i++) {
if(flags&testClutNegativeFlag){
put=((**(**(**device).gdPMap).pmTable)).ctTable[clutSize-1-i].rgb;
}else if(flags&testClutGains){
// Estimate rgb gains of any transformation
put.red=put.green=put.blue=0;
j=(0xffffL*i+(clutSize-1)/2)/(clutSize-1);
switch(i%3){
case 0:
put.red=j;
break;
case 1:
put.green=j;
break;
case 2:
put.blue=j;
break;
}
}else if(flags&testClutLinearFlag){
// Linear test pattern
put.red=put.green=put.blue=0;
j=0xffffffff*(i+(clutSize-1)/2)/(clutSize-1);
switch(i%4){
case 0:
put.red=put.green=put.blue=j;
break;
case 1:
put.red=j;
break;
case 2:
put.green=j;
break;
case 3:
put.blue=j;
break;
}
}else{
// Random test pattern
put.red=randU();
put.green=randU();
put.blue=randU();
}
if(!SixteenBitGray){
put.red&=0xff00;
put.green&=0xff00;
put.blue&=0xff00;
}
table[i].rgb=put;
}
return table;
}
OSErr WriteClut(GDHandle device,ColorSpec putTable[],short flags)
{
short i,clutSize=GDClutSize(device);
char priority=7;
int error;
SetEntriesFunction function;
if(flags&testClutQuicklyFlag)function=SetEntriesQuickly;
else function=GDSetEntriesByType;
if(flags&testClutSeriallyFlag){
// Load one clut entry at a time
for(i=0;i<clutSize;i++){
SwapPriority(&priority); // Force driver to load clut now.
error=(function)(device,i,0,&putTable[i]);
SwapPriority(&priority);
if(error)return error;
}
}else{
// Load whole clut at once
SwapPriority(&priority); // Force driver to load clut now.
error=(function)(device,0,clutSize-1,putTable);
SwapPriority(&priority);
if(error)return error;
}
return 0;
}
OSErr GDTestClutHash(short flags,VideoInfo *card){
SetEntriesFunction function;
int error;
short isGray,quickly;
VideoCardClutTest *clut;
if(card->device==NULL)return 0;
quickly=((flags&testClutQuicklyFlag)!=0);
isGray=!TestDeviceAttribute(card->device,gdDevType);
clut=&card->depth[card->d].clut[quickly][isGray];
if(clut->hash.tested==0)clut->hash.errors=0;
if(!clut->hash.doTest)return 0;
if(quickly)function=SetEntriesQuickly;
else function=GDSetEntriesByType;
error=VisibleHash(card->device,function,0,&clut->hash.errors);
clut->hash.tested=1;
return error;
}
OSErr VisibleHash(GDHandle device,SetEntriesFunction function,short clutEntries
,short *hashPtr)
{
int error;
short clutSize;
short hash;
long tick;
ColorSpec *putTable,*linearTable=NULL;
if(device==NULL || (**device).gdType==fixedType)return 0;
clutSize=GDClutSize(device);
if(clutEntries<0 || clutEntries>clutSize)return 1;
if(clutEntries==0)clutEntries=clutSize;
if((**device).gdType==directType){
if(function==GDSetEntries)function=GDDirectSetEntries;
putTable=linearTable=GDNewLinearColorTable(device);
if(linearTable==NULL)return MemError();
}else putTable=((**(**(**device).gdPMap).pmTable)).ctTable;
error=(function)(device,0,clutEntries-1,putTable);
if(!error){
printf(BLANKLINE);
if(device==GetMainDevice())
printf("Do you see any dynamic black specks on this screen? (No):");
else printf("Do you see any dynamic black specks on the test screen? (No):");
fflush(stdout);
do{
tick=TickCount();
do{
(function)(device,0,clutEntries-1,putTable);
}while(TickCount()-tick<30);
}while(!kbhit());
hash=YesOrNo(0);
printf("\r");
if(linearTable!=NULL)DisposePtr((Ptr)linearTable);
if(hashPtr!=NULL)*hashPtr=hash;
}
return error;
}
Boolean UnequalClutEntry(RGBColor *a,RGBColor *b,short mask)
{
return (a->red&mask)!=(b->red&mask)
||(a->green&mask)!=(b->green&mask)
||(a->blue&mask)!=(b->blue&mask);
}
/*
When you set a video screen to monochrome or "gray" (as opposed to "color"),
e.g. using the Control Panel:Monitors, the request is passed on to the video
driver. The video driver transforms each of your rgb triplets to a
luminance-equivalent gray, using a formula that must be very similar, if not
equivalent, to the code below. The rounding is bad, e.g. any gray rgb triplet
(i,i,i), other than (0,0,0), is transformed to (i-1,i-1,i-1), which is darker,
failing to preserve luminance. However, my goal was to replicate Apple's crumby
transformation, not to improve it. I presume that the reason that I have to trim
my numbers down a tad (-0.00001) is that I'm doing this with 80-bit precision
whereas the driver uses the 64-bit precision of the SANE routines. Presumably I
could obtain the same result by compiling this subroutine separately, to use
64-bit floating point, since all the arguments are ints.
*/
void RGBToGray(RGBColor *rgb,short dacSize)
// Empirical formula to replicate Apple's luminance mapping.
{
short i,shift;
unsigned long n;
if(!SixteenBitGray){
shift=16-dacSize;
i=(rgb->red>>shift)*(0.30-0.00001)
+(rgb->green>>shift)*(0.59-0.00001)+(rgb->blue>>shift)*(0.11);
n=0xffffffffUL/((1<<dacSize)-1);
rgb->red=rgb->green=rgb->blue=(i*n)>>16;
}else{
rgb->red=rgb->green=rgb->blue=rgb->red*(0.30-0.00001)
+rgb->green*(0.59)+rgb->blue*(0.11-0.00001);
}
}